package xyz.lwm.lazycat.setting;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONPath;
import xyz.lwm.lazycat.annotation.SettingPrefix;
import xyz.lwm.lazycat.utility.CustomKeyMap;
import xyz.lwm.lazycat.utility.It;
import xyz.lwm.lazycat.utility.StrPool;
import xyz.lwm.lazycat.utility.convert.Convert;
import xyz.lwm.lazycat.utility.convert.Types;
import xyz.lwm.lazycat.utility.reflect.AnnotationUtil;
import xyz.lwm.lazycat.utility.reflect.TypeRef;
import xyz.lwm.lazycat.utility.reflect.TypeUtil;

import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static xyz.lwm.lazycat.utility.StringUtil.fromCamel;
import static xyz.lwm.lazycat.utility.StringUtil.isBlank;
import static xyz.lwm.lazycat.utility.StringUtil.toCamel;

/**
 * implement instance
 *
 * @author lwm
 */
class SettingImpl implements Setting, SettingSetter {

    private final CustomKeyMap<String, String> map = new CustomKeyMap<>(k ->
        k == null ? null : fromCamel(k, StrPool.CHAR_DASHED)
    );

    @Override
    public Setting forPath(String prefix) {
        if (isBlank(prefix)) {
            return this;
        }
        SettingImpl pathEnv = new SettingImpl();
        Map<String, String> strMap = asMap();
        strMap.forEach((k, v) -> {
            if (k != null && v != null) {
                String objKey = prefix + ".";
                String arrKey = prefix + "[";
                // 处理对象键值对
                if (k.startsWith(objKey)) {
                    pathEnv.set(k.substring(objKey.length()), v);
                }
                // 处理数组键值对
                else if (k.startsWith(arrKey)) {
                    pathEnv.set(k.substring(prefix.length()), v);
                }
            }
        });
        return pathEnv;
    }

    @Override
    public <E> E forPath(Class<E> clz) throws SettingException {
        SettingPrefix prefix = AnnotationUtil.getAnnotations(clz).getAnnotation(SettingPrefix.class);
        if (prefix == null) {
            throw new SettingException("Class " + clz.getName() +
                    " must be annotated with @xyz.lwm.lazycat.annotation.SettingPrefix");
        }
        return forPath(prefix.value()).toBean(clz);
    }

    @Override
    public Map<String, String> asMap() {
        return new HashMap<>(map);
    }

    @Override
    public It<String> get(String key) {
        return It.of(map.get(key)).let(Object::toString);
    }

    @Override
    public <T> It<T> get(String key, Class<T> clz) {
        return It.of(map.get(key))
                .let(v -> Convert.to(v, clz));
    }

    @Override
    public <E> E toBean(Class<E> clz) {
        return toBean((Type) clz);
    }

    @Override
    public <E> E toBean(TypeRef<E> typeRef) {
        return toBean(typeRef.getArgumentType());
    }

    @SuppressWarnings("unchecked")
    private <E> E toBean(Type type) {
        Class<?> clz = TypeUtil.getClass(type);
        if (Types.isBaseType(clz)) {
            throw new SettingException("It's basic type or simple value type, please use " +
                    "get(java.lang.String, java.lang.Class<T>)");
        }
        // create root json
        Object json = clz.isArray() || Collection.class.isAssignableFrom(clz) ?
                new JSONArray() : new JSONObject();
        // set by path
        asMap().forEach((k, v) -> {
            if (k != null && v != null) {
                String key = toCamel(k, StrPool.CHAR_DASHED);
                JSONPath.set(json, key, v);
            }
        });
        // to java object
        return (E) JSON.to(clz, json);
    }

    @Override
    public void set(String key, Object val) {
        map.put(key, Objects.toString(val));
    }

}
